// praxis/services/machine_simulator.cc
#include "praxis/services/machine_simulator.h"

#include <vector>

#include "praxis/hardware/iactuator.h"
#include "praxis/hardware/simulated_sensor.h"  // Needed for static_pointer_cast
#include "praxis/services/actuator_manager.h"
#include "praxis/services/sensor_manager.h"

namespace praxis {
struct MachineSimulator::Impl {
  std::unique_ptr<CSensorManager> sensor_manager;
  std::unique_ptr<CActuatorManager> actuator_manager;
  json current_state;
  std::vector<StateObserver> observers;
  int timer_tick = 0;

  explicit Impl(const json& machine_schema);
  void LinkHardware();
  void Update();
  void NotifyObservers();
};

MachineSimulator::Impl::Impl(const json& machine_schema) {
  if (machine_schema.contains("sensor_configuration")) {
    sensor_manager = std::make_unique<CSensorManager>(
        machine_schema["sensor_configuration"]);
  }
  if (machine_schema.contains("actuator_configuration")) {
    actuator_manager = std::make_unique<CActuatorManager>(
        machine_schema["actuator_configuration"]);
  }
  LinkHardware();
}

void MachineSimulator::Impl::LinkHardware() {
  if (!sensor_manager || !actuator_manager) return;

  const auto& sensors = sensor_manager->GetAllSensors();
  for (const auto& pair : sensors) {
    const std::string& sensor_name = pair.first;
    auto sensor_base = pair.second;
    const json& config = sensor_manager->GetSensorConfig(sensor_name);

    if (config.contains("linked_actuator")) {
      std::string actuator_name = config["linked_actuator"];
      auto actuator = actuator_manager->GetActuator(actuator_name);
      if (actuator) {
        auto sim_sensor =
            std::static_pointer_cast<CSimulatedSensor>(sensor_base);
        double influence = config.value("actuator_influence", 1.0);
        sim_sensor->LinkToActuator(actuator, influence);
      }
    }
  }
}

void MachineSimulator::Impl::Update() {
  timer_tick++;

  // 1. Simulate a simple control logic (e.g., recipe running)
  if (actuator_manager) {
    if (auto heater = actuator_manager->GetActuator("Heater")) {
      heater->SetTarget(75.0);  // Set heater to 75% power
    }
    if (auto mfc = actuator_manager->GetActuator("MFC_SiH4")) {
      mfc->SetTarget(20.0);  // Set gas flow to 20 sccm
    }
  }

  // 2. Update actuators (they move toward their targets)
  if (actuator_manager) {
    actuator_manager->UpdateAllActuators();
  }

  // 3. Update sensors (their values change based on actuator states)
  if (sensor_manager) {
    sensor_manager->UpdateAllSensors();
    current_state.update(sensor_manager->GetCurrentValues());
  }

  // 4. Update other non-hardware state variables
  current_state["process_timer"] = timer_tick / 2.0;  // Seconds
  current_state["recipe_running_step"] = "SIMULATING_PROCESS";

  NotifyObservers();
}

void MachineSimulator::Impl::NotifyObservers() {
  for (const auto& obs : observers) {
    if (obs) {
      obs(current_state);
    }
  }
}

// Public Class Implementation
MachineSimulator::MachineSimulator(const json& machine_schema)
    : impl_(std::make_unique<Impl>(machine_schema)) {}

MachineSimulator::~MachineSimulator() = default;

void MachineSimulator::RegisterObserver(StateObserver observer) {
  impl_->observers.push_back(observer);
}

void MachineSimulator::Update() { impl_->Update(); }
}  // namespace praxis